package com.uh.rds.tester.standalone;

import com.uh.rds.testing.config.TestConfig;
import com.uh.rds.testing.config.TestConfigManager;
import com.uh.rds.testing.conn.ConnectionMode;
import com.uh.rds.testing.conn.Endpoint;
import com.uh.rds.testing.conn.RdsConnectInfo;
import com.uh.rds.testing.conn.Shard;
import com.uh.rds.testing.data.FileTask;
import com.uh.rds.testing.data.TestDataGenerator;
import com.uh.rds.testing.utils.RdsConnectionUtils;
import com.uh.rds.testing.validator.DataValidatorArguments;
import com.uh.rds.testing.validator.DataValidatorResult;
import com.uh.rds.testing.validator.DataValidatorRunner;
import com.uh.rds.testing.validator.types.*;
import static com.uh.rds.testing.base.DataValidatorStep.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.List;



/**
 * 测试主备哨兵数据同步：验证同步数据一致性和时效性；
 */
public class SentinelDataSyncTest {

    private Logger logger = LoggerFactory.getLogger(SentinelDataSyncTest.class);

    private TestConfig conf;

    private RdsConnectInfo connection;

    File baseDir = new File("data/SentinelDataSyncTest");
    String stringDataFile = "string-data.csv";
    String setDataFile = "set-data.csv";
    String hashDataFile = "hash-data.csv";
    String listDataFile = "list-data.csv";
    String zsetDataFile = "zset-data.csv";
    String streamDataFile = "stream-data.csv";


    /**
     * 初始化测试环境
     */
    public void init() {
        logger.info("SentinelDataSyncTest init ...");
        conf = TestConfigManager.getTestConfig("SentinelDataSyncTest");

        // 读取配置生成连接信息
        List<String> shards = conf.getList("shards");
        List<String> writingEndpoints = conf.getList("writingEndpoints");
        List<String> readingEndpoints = conf.getList("readingEndpoints");
        String password = conf.getString("password");

        connection = new RdsConnectInfo(ConnectionMode.CLUSTER, password);
        for(int i = 0; i < shards.size(); i++) {
            connection.addShard(new Shard(i, shards.get(i)));
        }

        for(int i = 0; i < writingEndpoints.size(); i++) {
            String endpoint = writingEndpoints.get(i);
            String[] parts = endpoint.split(":");
            connection.addEndpoint(new Endpoint(parts[0], Integer.parseInt(parts[1]), true, i));
        }

        for(int i = 0; i < readingEndpoints.size(); i++) {
            String endpoint = readingEndpoints.get(i);
            String[] parts = endpoint.split(":");
            connection.addEndpoint(new Endpoint(parts[0], Integer.parseInt(parts[1]), false, i));
        }

        logger.info("connection: " + connection);
    }


    /**
     * 生成测试数据，第一次生成后之后可重复使用。
     * 会判断数据文件是否存在，如果存在则不再生成。
     */
    public void generateTestData() {
        logger.info("Generating test data ...");
        int dataCount = conf.getInteger("dataCount");
        int batchCount = conf.getInteger("batchCount");
        int valueLengthMin = conf.getInteger("valueLengthMin");
        int valueLengthMax = conf.getInteger("valueLengthMax");
        TestDataGenerator dataGenerator = new TestDataGenerator();

        boolean hasDataTask = false;
        for(int i = 0 ; i < batchCount ; i++) {
            File batchDir = new File(baseDir, "batch-" + i);
            if(batchDir.exists() == false) { //批量目录不存在才生成数据
                File stringDataFile = new File(batchDir, this.stringDataFile);
                File setDataFile = new File(batchDir, this.setDataFile);
                File hashDataFile = new File(batchDir, this.hashDataFile);
                File listDataFile = new File(batchDir, this.listDataFile);
                File zsetDataFile = new File(batchDir, this.zsetDataFile);
                File streamDataFile = new File(batchDir, this.streamDataFile);

                dataGenerator.addTask(new FileTask(stringDataFile, "string", dataCount, 1, new int[] {valueLengthMin, valueLengthMax}));
                dataGenerator.addTask(new FileTask(setDataFile, "set", dataCount, 1, new int[] {valueLengthMin, valueLengthMax}));
                dataGenerator.addTask(new FileTask(hashDataFile, "hash", dataCount, 1, new int[] {valueLengthMin, valueLengthMax}));
                dataGenerator.addTask(new FileTask(listDataFile, "list", dataCount, 1, new int[] {valueLengthMin, valueLengthMax}));
                dataGenerator.addTask(new FileTask(zsetDataFile, "zset", dataCount, 1, new int[] {valueLengthMin, valueLengthMax}));
                dataGenerator.addTask(new FileTask(streamDataFile, "stream", dataCount, 1, new int[] {valueLengthMin, valueLengthMax}));

                hasDataTask = true;
            }
        }

        if(hasDataTask) {
            dataGenerator.executeTasks();
        }
        else {
            logger.info("Test data already exists, skip generating ...");
        }

    }


    public void insertData() throws Exception {
        int batchCount = conf.getInteger("batchCount");

        List<DataValidatorResult.ValidatorSummary> validatorSummaryList = new ArrayList<>();
        for (int i = 0; i < batchCount; i++) {
            DataValidatorRunner tester = new DataValidatorRunner();

            File batchDir = new File(baseDir, "batch-" + i);
            File stringDataFile = new File(batchDir, this.stringDataFile);
            File setDataFile = new File(batchDir, this.setDataFile);
            File hashDataFile = new File(batchDir, this.hashDataFile);
            File listDataFile = new File(batchDir, this.listDataFile);
            File zsetDataFile = new File(batchDir, this.zsetDataFile);
            File streamDataFile = new File(batchDir, this.streamDataFile);


            tester.addValidator(StringDataValidator.class, stringDataFile);
            tester.addValidator(SetDataValidator.class, setDataFile);
            tester.addValidator(HashDataValidator.class, hashDataFile);
            tester.addValidator(ListDataValidator.class, listDataFile);
            tester.addValidator(ZsetDataValidator.class, zsetDataFile);
            tester.addValidator(StreamDataValidator.class, streamDataFile);

            DataValidatorArguments config = new DataValidatorArguments();
            config.setValidateFromSlave(true);
            config.setThreadCount(conf.getInteger("threadCount"));
            config.setValidateInterval(conf.getLong("validateInterval"));
            tester.setConfig(config);

            tester.prepareThreadsData(connection);
            logger.info("Tester-" + i + " prepared.");
            DataValidatorResult result  = null;
            if(conf.getBoolean("clearData")){
                result = tester.runTest(PROCESS, CLEAR);
            }else {
                result = tester.runTest(PROCESS);
            }
            if(result.isPassed()) {
                System.out.println(result.summary());
                System.out.println("Batch-" + i + " data inserted!");
            } else {
                System.err.println(result.summary());
                System.out.println("Batch-" + i + " data inserted!");
            }
            validatorSummaryList.addAll(result.validatorSummaryList());

        }

        DataValidatorResult.validatorSummaryList(validatorSummaryList);
    }


    public void validateData() throws Exception {
        int batchCount = conf.getInteger("batchCount");

        List<DataValidatorResult.ValidatorSummary> validatorSummaryList = new ArrayList<>();
        for (int i = 0; i < batchCount; i++) {
            DataValidatorRunner tester = new DataValidatorRunner();

            File batchDir = new File(baseDir, "batch-" + i);
            File stringDataFile = new File(batchDir, this.stringDataFile);
            File setDataFile = new File(batchDir, this.setDataFile);
            File hashDataFile = new File(batchDir, this.hashDataFile);
            File listDataFile = new File(batchDir, this.listDataFile);
            File zsetDataFile = new File(batchDir, this.zsetDataFile);
            File streamDataFile = new File(batchDir, this.streamDataFile);


            tester.addValidator(StringDataValidator.class, stringDataFile);
            tester.addValidator(SetDataValidator.class, setDataFile);
            tester.addValidator(HashDataValidator.class, hashDataFile);
            tester.addValidator(ListDataValidator.class, listDataFile);
            tester.addValidator(ZsetDataValidator.class, zsetDataFile);
            tester.addValidator(StreamDataValidator.class, streamDataFile);

            DataValidatorArguments config = new DataValidatorArguments();
            config.setValidateFromSlave(true);
            config.setThreadCount(conf.getInteger("threadCount"));
            config.setValidateInterval(conf.getLong("validateInterval"));
            tester.setConfig(config);

            tester.prepareThreadsData(connection);
            logger.info("Tester-" + i + " prepared.");
            DataValidatorResult result  = null;
            if(conf.getBoolean("clearData")){
                result = tester.runTest(VALIDATE, CLEAR);
            }else {
                result = tester.runTest(VALIDATE);
            }
            if(result.isPassed()) {
                System.out.println(result.summary());
                System.out.println("Batch-" + i + " data validateed!");
            } else {
                System.err.println(result.summary());
                System.out.println("Batch-" + i + " data validateed!");
            }
            validatorSummaryList.addAll(result.validatorSummaryList());

        }

        DataValidatorResult.validatorSummaryList(validatorSummaryList);
    }

    public void test() throws Exception {
        logger.info("SentinelDataSyncTest test ...");
        init();
        generateTestData();
        //是否执行前清空数据
        if(conf.getBoolean("clearDataF")){
            RdsConnectionUtils.flushDb(connection);
        }
        //在主哨兵中插入数据
        insertData();

        //等待时间启动从哨兵服务，添加同步配置
        Integer startSlaveSentinelService = conf.getInteger("startSlaveSentinelService");
        System.out.println("主哨兵服务插入数据成功，启动从哨兵服务，填写同步配置。。。。");
        Thread.sleep(startSlaveSentinelService * 1000);
        System.out.println("从哨兵服务启动成功，同步配置完成，开始验证主从哨兵服务数据是否同步成功！");

        //验证主从哨兵数据是否同步成功
        validateData();
    }


    public static void main(String[] args) throws Exception  {
        SentinelDataSyncTest test = new SentinelDataSyncTest();
        test.test();
    }
}
